-module(sizeof).
-compile([export_all]).

%% XXX check typeof/sizeof for badarg on tuple of size!=2

pow2(Power) -> util:pow2(Power).

signed_ints() -> [int8, int16, int32, int64].
unsigned_ints() -> [uint8, uint16, uint32, uint64].
base_ints() -> signed_ints() ++ unsigned_ints().
floats() -> [float, double, longdouble].
all_base_types() -> base_ints() ++ floats() ++ [ptr, void].

platform_ints() -> [byte, ubyte, short, ushort, int, uint,
                    long, ulong, longlong, ulonglong].

all_ints() -> base_ints() ++ platform_ints().

one_of(_X, []) -> false;
one_of(X, [X|_Rest]) -> true;
one_of(X, [_Y|Rest]) -> one_of(X, Rest).

check_typeof_tags() ->
    BaseTypes = all_base_types(),
    BaseTypes = [ffi:typeof(X) || X <- all_base_types()],
    true = one_of(ffi:typeof(int),      signed_ints()),
    true = one_of(ffi:typeof(byte),     signed_ints()),
    true = one_of(ffi:typeof(short),    signed_ints()),
    true = one_of(ffi:typeof(long),     signed_ints()),
    true = one_of(ffi:typeof(longlong), signed_ints()),
    true = one_of(ffi:typeof(uint),      unsigned_ints()),
    true = one_of(ffi:typeof(ubyte),     unsigned_ints()),
    true = one_of(ffi:typeof(ushort),    unsigned_ints()),
    true = one_of(ffi:typeof(ulong),     unsigned_ints()),
    true = one_of(ffi:typeof(ulonglong), unsigned_ints()),
    {'EXIT', {badarg, _}}  = (catch ffi:typeof(unexistant)).

check_typeof_values() ->
    Int = ffi:typeof(int),
    Int = ffi:typeof(0),
    Int = ffi:typeof({int, 0}),
    ptr = ffi:typeof(null),
    ptr = ffi:typeof({ptr, 12345}),
    ptr = ffi:typeof("hello"),
    ptr = ffi:typeof(<<"world">>),
    ptr = ffi:typeof(""),
    ptr = ffi:typeof([]),
    double = ffi:typeof(3.141).

check_sizeof() ->
    1 = ffi:sizeof(int8),
    1 = ffi:sizeof(uint8),
    2 = ffi:sizeof(int16),
    2 = ffi:sizeof(uint16),
    4 = ffi:sizeof(int32),
    4 = ffi:sizeof(uint32),
    8 = ffi:sizeof(int64),
    8 = ffi:sizeof(uint64),
    Int = ffi:sizeof(int),
    Int = ffi:sizeof(1),
    Int = ffi:sizeof({int, 1}),
    true = (Int == 4) or (Int == 8),
    Ptr = ffi:sizeof(ptr),
    true = (Ptr == 4) or (Ptr == 8),
    Float = ffi:sizeof(float), 
    Double = ffi:sizeof(double), 
    Longdouble = ffi:sizeof(longdouble), 
    true = Float > 0,
    true = Double >= Float,
    true = Longdouble >= Double.

check_badarg(Type, Value) ->
    {'EXIT', {badarg, _}} = (catch ffi:typeof({Type, Value})),
    {'EXIT', {badarg, _}} = (catch ffi:sizeof({Type, Value})).

integer_points() ->
    Points = [0] ++ lists:flatten([[pow2(X-1)-1, -pow2(X-1), pow2(X)-1] || 
                                      X <- [1, 8, 16, 32, 64, 128]]),
    Points ++
        lists:map(fun(X) -> X + 1 end, Points) ++
        lists:map(fun(X) -> X - 1 end, Points).

integer_points_in(Max, Min) ->
    lists:filter(fun(X) -> (X >= Min) and (X =< Max) end,
                 integer_points()).

integer_points_out(Min, Max) ->
    lists:filter(fun(X) -> (X < Min) and (X > Max) end,
                 integer_points()).

check_int(Type, ExpType, Min, Max) ->
    [ExpType = ffi:typeof({Type, X}) || X <- integer_points_in(Min, Max)],
    [{'EXIT', {badarg, _}} =
     (catch ffi:typeof({Type, X})) || X <- integer_points_out(Min, Max)].

check_int(Type, Min, Max) ->
    check_int(Type, Type, Min, Max).

check_number(ExpType, Min, Max) ->
    [ExpType = ffi:typeof(X) || X <- integer_points_in(Min, Max)],
    [{'EXIT', {badarg, _}} =
     (catch ffi:typeof(X)) || X <- integer_points_out(Min, Max)].

check_number() ->
    Type = ffi:typeof(2007),
    Size = ffi:sizeof(Type),
    check_number(Type, -pow2(Size-1), pow2(Size-1)-1).

check_uint(Type) ->
    Size = ffi:sizeof(Type),
    check_int(Type, 0, pow2(Size)-1).

check_sint(Type) ->
    Size = ffi:sizeof(Type),
    check_int(Type, -pow2(Size-1), pow2(Size-1)-1).

check_ints() ->
    check_number(),
    check_sint(int8),
    check_sint(int16),
    check_sint(int32),
    check_sint(int64),
    check_uint(uint8),
    check_uint(uint16),
    check_uint(uint32),
    check_uint(uint64).

main() ->
    check_typeof_tags(),
    check_typeof_values(),
    check_sizeof(),
    check_ints(),
    util:die().

